home *** CD-ROM | disk | FTP | other *** search
/ Sprite 1984 - 1993 / Sprite 1984 - 1993.iso / src / lib / jaq / dist / jcopy.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-09-01  |  15.7 KB  |  630 lines

  1. /* 
  2.  * jcopy.c --
  3.  *
  4.  *    Perform volume-to-volume copy on the Jaquith system
  5.  *
  6.  * Copyright 1991 Regents of the University of California
  7.  * Permission to use, copy, modify, and distribute this
  8.  * software and its documentation for any purpose and without
  9.  * fee is hereby granted, provided that the above copyright
  10.  * notice appear in all copies.  The University of California
  11.  * makes no representations about the suitability of this
  12.  * software for any purpose.  It is provided "as is" without
  13.  * express or implied warranty.
  14.  *
  15.  * Quote:
  16.  *     "For every problem there is a solution which is
  17.  *     simple, clean and wrong."
  18.  * -- Henry Louis Mencken
  19.  */
  20.  
  21. #ifndef lint
  22. static char rcsid[] = "$Header: /sprite/lib/forms/RCS/jquery.c,v 1.0 91/01/07 18:02:37 mottsmth Exp $ SPRITE (Berkeley)";
  23. #endif /* not lint */
  24.  
  25. #include "jaquith.h"
  26. #include "option.h"
  27.  
  28. static char printBuf[T_MAXSTRINGLEN];
  29.  
  30. static FILE *memDbg = NULL;   /* stream for memory tracing */
  31.  
  32. int jDebug;                   /* Internal debugging only */
  33. int syserr = 0;               /* Our personal record of errno */
  34.  
  35. static char hostName[80];
  36. static char userName[80];
  37.  
  38. #define DEF_SRCVOL -1
  39. #define DEF_DESTVOL -1
  40. #define DEF_VERBOSE 0
  41. #define DEF_UPDATEFREQ 50
  42.  
  43. typedef struct MgrData {
  44.     int sock;
  45.     int volId;
  46.     int stream;
  47. } MgrData;
  48.  
  49. static void  CheckOptions   _ARGS_ ((Parms *parmsPtr));
  50. static int   GetDeviceCnt   _ARGS_ ((int sock, int *countPtr));
  51. static int   MgrAcquire     _ARGS_ ((MgrData *mgrDataPtr));
  52. static int   MgrRelease     _ARGS_ ((MgrData *mgrDataPtr));
  53. static int   PerformCopy    _ARGS_ ((int srcVol, int destVol));
  54. static void  CopyVolume     _ARGS_ ((int srcVol, int destVol));
  55.  
  56. typedef struct parmTag {
  57.     int srcVol;
  58.     int destVol;
  59.     char *root;
  60.     char *mgrServer;
  61.     int mgrPort;
  62.     int verbose;
  63.     int updateFreq;
  64.     int bufSize;
  65.     char *volFile;
  66. } Parms;
  67.  
  68. Parms parms = {
  69.     DEF_SRCVOL,
  70.     DEF_DESTVOL,
  71.     DEF_ROOT,
  72.     DEF_MGRSERVER,
  73.     DEF_MGRPORT,
  74.     DEF_VERBOSE,
  75.     DEF_UPDATEFREQ,
  76.     T_TARSIZE,
  77.     DEF_VOLFILE
  78. };
  79.  
  80. Option optionArray[] = {
  81.     {OPT_INT, "srcvol", (char *)&parms.srcVol, "Source volume name"},
  82.     {OPT_INT, "destvol", (char *)&parms.destVol, "Destination volume name"},
  83.     {OPT_STRING, "root", (char *)&parms.root, "root of index tree"},
  84.     {OPT_STRING, "mgrserver", (char *)&parms.mgrServer, "Hostname of jukebox server"},
  85.     {OPT_INT, "mgrport", (char *)&parms.mgrPort, "Port of jukebox server"},
  86.     {OPT_TRUE, "v", (char *)&parms.verbose, "Verbose mode"},
  87.     {OPT_INT, "updatefreq", (char *)&parms.updateFreq, "With -v flag, the frequency of copy status update"},
  88.     {OPT_INT, "bufsize", (char *)&parms.bufSize, "Write unit in bytes"},
  89.     {OPT_STRING, "volfile", (char *)&parms.volFile, "Volume configuration file"}
  90. };
  91. int numOptions = sizeof(optionArray) / sizeof(Option);
  92.  
  93.  
  94. /*
  95.  *----------------------------------------------------------------------
  96.  *
  97.  * jcopy --
  98.  *
  99.  *    Main driver for volume-to-volume copy operation
  100.  *
  101.  * Results:
  102.  *    none.
  103.  *
  104.  * Side effects:
  105.  *    none.
  106.  *
  107.  *----------------------------------------------------------------------
  108.  */
  109.  
  110. int
  111. main(argc, argv)
  112.     int argc;                 /* See Option Array */
  113.     char *argv[];
  114. {
  115.     int retCode;
  116.     char *myName;
  117.     uid_t myUid;
  118.  
  119. /*    memDbg = fopen("jcopy.mem","w"); */
  120.     MEM_CONTROL(8192, memDbg, TRACEMEM+TRACECALLS, 4096);
  121.  
  122.     argc = Opt_Parse(argc, argv, optionArray, numOptions, 0);
  123.  
  124.     CheckOptions(&parms);
  125.  
  126.     myUid = geteuid();
  127.     myName = Utils_GetLoginByUid(myUid);
  128.     strcpy(userName, myName);
  129.     gethostname(hostName, sizeof(hostName));
  130.  
  131.     CopyVolume(parms.srcVol, parms.destVol);
  132.  
  133.     MEM_REPORT("jcopy", ALLROUTINES, SORTBYOWNER);
  134.  
  135.     return T_SUCCESS;
  136. }
  137.  
  138.  
  139.  
  140. /*
  141.  *----------------------------------------------------------------------
  142.  *
  143.  * CopyVolume -- 
  144.  *
  145.  *      Copy one Jaquith volume's contents to another.
  146.  *
  147.  * Results:
  148.  *    none.
  149.  *
  150.  * Side effects:
  151.  *    None.
  152.  *
  153.  * Note:
  154.  *      The idea is that an old tape is being rolled forward to a new one.
  155.  *      We want to keep the Jaquith files up to date so we know that
  156.  *      the volume is gone. 
  157.  *
  158.  *----------------------------------------------------------------------
  159.  */
  160.  
  161. static void
  162. CopyVolume(srcVol, destVol)
  163.     int srcVol;               /* source volume Id */
  164.     int destVol;              /* destination volume Id */
  165. {
  166.     int count = 0;
  167.     int retCode;
  168.     VolOwner *volOwnerPtr;
  169.     VolConfig *volList;
  170.     VolConfig *itemPtr;
  171.     int volCnt;
  172.     int i;
  173.     char filePath[T_MAXPATHLEN];
  174.  
  175.     /*
  176.      * Quick check to see if a tape-to-tape copy is even possible here
  177.      */
  178.     if (GetDeviceCnt(&count) != T_SUCCESS) {
  179.         sprintf(printBuf,"Couldn't get device count from jmgr\n");
  180.         Utils_Bailout(printBuf, BAIL_PRINT);
  181.     }
  182.  
  183.     if (count < 2) {
  184.         sprintf(printBuf,"Jmgr has only %d device. Can't do copy.\n", count);
  185.         Utils_Bailout(printBuf, BAIL_PRINT);
  186.     }
  187.  
  188.     /*
  189.      * Acquire a new volume if one wasn't specified
  190.      */
  191.     if ((destVol == DEF_DESTVOL) ||
  192.     (Utils_GetOk("Remove destination volume from free list? [y/n] "))) {
  193.     if (Admin_GetFreeVol(parms.root, &destVol) != T_SUCCESS) {
  194.         sprintf(printBuf, "Couldn't get take volume from free list. errno %d\n", syserr);
  195.         Utils_Bailout(printBuf, BAIL_PRINT);
  196.     } else if (parms.verbose) {
  197.         fprintf(stdout, "Volume %d removed from free list.\n", destVol);
  198.     }
  199.     }
  200.  
  201.     /*
  202.      * Safety overwrite check
  203.      */
  204.     if (destVol == srcVol) {
  205.     sprintf(printBuf, "Source and destination volumes aren't distinct.\n");
  206.     Utils_Bailout(printBuf, BAIL_PRINT);
  207.     }
  208.  
  209.     /*
  210.      * Destination volume already assigned to someone ?
  211.      */
  212.     volOwnerPtr = Admin_FindVolOwner(destVol, parms.root, "*.arch");
  213.     if ((volOwnerPtr != NULL) &&
  214.     (volOwnerPtr->owner != NULL)) {
  215.     /* zap trailing '.arch' */
  216.     volOwnerPtr->owner[strlen(volOwnerPtr->owner)-5] = '\0';
  217.     sprintf(printBuf,
  218.         "Volume %d already owned by archive '%s'. Continue? [y/n] ",
  219.         destVol, volOwnerPtr->owner);
  220.     if (!Utils_GetOk(printBuf)) {
  221.         exit(-1);
  222.     }
  223.     }
  224.  
  225.     /*
  226.      * Do we even know about this tape ?
  227.      */
  228.     volOwnerPtr = Admin_FindVolOwner(srcVol, parms.root, "*.arch");
  229.     if ((volOwnerPtr != NULL) &&
  230.     (volOwnerPtr->owner == NULL)) {
  231.     if (!Utils_GetOk("Source volume isn't owned by any archive. Contine? [y/n] ")) {
  232.         exit(-1);
  233.     }
  234.     }
  235.  
  236.     /*
  237.      * Should source volume be retired ?
  238.      */
  239.     if (Utils_GetOk("Should source volume be removed from use? [y/n] ")) {
  240.     Admin_ReadVolConfig(parms.volFile, volList, &volCnt);
  241.     volList = (VolConfig *)MEM_ALLOC("CopyVolume",
  242.                      volCnt*sizeof(VolConfig));
  243.     if (Admin_ReadVolConfig(parms.volFile, volList, &volCnt)!= T_SUCCESS) {
  244.         sprintf(printBuf,"Couldn't read %s. Errno %d.\n",
  245.             parms.volFile, syserr);
  246.         volCnt = 0;
  247.     }
  248.     for (i=0,itemPtr=volList; i<volCnt; i++,itemPtr++) {
  249.         if (itemPtr->volId == srcVol) {
  250.         break;
  251.         }
  252.     }
  253.     if (i >= volCnt) {
  254.         sprintf(printBuf,
  255.             "Couldn't find source volume in %s. Continue? [y/n] ",
  256.             parms.volFile);
  257.         if (!Utils_GetOk(printBuf)) {
  258.         exit(-1);
  259.         }
  260.     } else if (Admin_WriteVolConfig(parms.volFile, volList, volCnt)
  261.            != T_SUCCESS) {
  262.         sprintf(printBuf,
  263.             "Couldn't remove source volume from %s. Errno %d. Continue? [y/n] ",
  264.             parms.volFile, syserr);
  265.         if (!Utils_GetOk(printBuf)) {
  266.         exit(-1);
  267.         }
  268.     }
  269.     MEM_FREE("CopyVolume", volList);
  270.     }
  271.  
  272.     /*
  273.      * Finally, we can do the copy itself
  274.      */
  275.     retCode = PerformCopy(srcVol, destVol);
  276.     
  277.     if ((retCode != T_SUCCESS) || (parms.verbose)) {
  278.         fprintf(stderr,"Done with return code %d.\n", retCode);
  279.     }
  280. }
  281.  
  282.  
  283.  
  284. /*
  285.  *----------------------------------------------------------------------
  286.  *
  287.  * PerformCopy --
  288.  *
  289.  *    Obtain the volumes and copy source stream to destination stream
  290.  *
  291.  * Results:
  292.  *    none.
  293.  *
  294.  * Side effects:
  295.  *    none.
  296.  *
  297.  *----------------------------------------------------------------------
  298.  */
  299.  
  300. static int
  301. PerformCopy(srcVol, destVol)
  302.     int srcVol;               /* source volume id */
  303.     int destVol;              /* destination volume id */
  304. {
  305.     int srcStream;
  306.     int destStream;
  307.     char *buf;
  308.     int len = 0;
  309.     int totCnt = 0;
  310.     int updateCnt = 0;
  311.     int lineCnt = 0;
  312.     MgrData mgrSrc;
  313.     MgrData mgrDest;
  314.  
  315.     buf = MEM_ALLOC("PerformCopy", parms.bufSize);
  316.  
  317.     mgrSrc.volId = srcVol;
  318.     if (MgrAcquire(&mgrSrc) != T_SUCCESS) {
  319.         sprintf(printBuf,"Couldn't get source volume %d.\n", srcVol);
  320.     Utils_Bailout(printBuf, BAIL_PRINT);
  321.     }
  322.     if (parms.verbose) {
  323.         fprintf(stderr,"Mounted source volume %d.\n", srcVol);
  324.     }
  325.  
  326.     mgrDest.volId = destVol;
  327.     if (MgrAcquire(&mgrDest) != T_SUCCESS) {
  328.         sprintf(printBuf,"Couldn't get destination volume %d.\n", destVol);
  329.     Utils_Bailout(printBuf, BAIL_PRINT);
  330.     }
  331.     if (parms.verbose) {
  332.         fprintf(stderr,"Mounted destination volume %d.\n", destVol);
  333.     }
  334.  
  335.     if (Dev_SeekVolume(srcStream, 0, DEV_ABSOLUTE) != T_SUCCESS) {
  336.     fprintf(stderr,"Couldn't rewind source volume\n. errno %d\n",
  337.         syserr);
  338.     fflush(stderr);
  339.     return T_IOFAILED;
  340.     } else if (parms.verbose) {
  341.     fprintf(stdout,"Rewound source volume.\n");
  342.     fflush(stdout);
  343.     }
  344.  
  345.     if (Dev_SeekVolume(destStream, 0, DEV_ABSOLUTE) != T_SUCCESS) {
  346.     fprintf(stderr,"Couldn't rewind destination volume\n. errno %d\n",
  347.         syserr);
  348.     return T_IOFAILED;
  349.     } else if (parms.verbose) {
  350.     fprintf(stdout,"Rewound destination volume.\n");
  351.     fflush(stdout);
  352.     }
  353.  
  354.     if (parms.verbose) {
  355.     fprintf(stdout,"Copying...");
  356.     fflush(stdout);
  357.     }
  358.  
  359.     while (len != EOF) {
  360.     while ((len=Dev_ReadVolume(srcStream, buf, parms.bufSize)) > 0) {
  361.         if (Dev_WriteVolume(destStream, buf, len) != len) {
  362.         fprintf(stderr,"\nShort write. errno %d\n", syserr);
  363.         fflush(stderr);
  364.         return T_IOFAILED;
  365.         }
  366.     }
  367.     if (len != EOF) {
  368.         totCnt++;
  369.     }
  370.     Dev_WriteEOF(destStream, 1);
  371.     if ((parms.verbose) && (++updateCnt == parms.updateFreq)) {
  372.         updateCnt = 0;
  373.         if (lineCnt++ == 10) {
  374.         lineCnt = 0;
  375.         fputc('\n', stdout);
  376.         }
  377.         fprintf(stdout, " %d", totCnt);
  378.         fflush(stdout);
  379.     }
  380.     }
  381.  
  382.     if (parms.verbose) {
  383.     fprintf(stdout, "\nCopied %d files.\n", totCnt);
  384.     fflush(stdout);
  385.     }
  386.  
  387.     if (MgrRelease(&mgrSrc) != T_SUCCESS) {
  388.         fprintf(stderr,"Couldn't release volume %d.\n", srcVol);
  389.     }
  390.  
  391.     if (MgrRelease(&mgrDest) != T_SUCCESS) {
  392.         fprintf(stderr,"Couldn't release volume %d.\n", destVol);
  393.     }
  394.  
  395.     MEM_FREE("PerformCopy", buf);
  396.  
  397.     return T_SUCCESS;
  398.  
  399. }
  400.  
  401.  
  402.  
  403. /*
  404.  *----------------------------------------------------------------------
  405.  *
  406.  * GetDeviceCnt --
  407.  *
  408.  *    Call jukebox manager to get device count
  409.  *
  410.  * Results:
  411.  *    none.
  412.  *
  413.  * Side effects:
  414.  *    none.
  415.  *
  416.  *----------------------------------------------------------------------
  417.  */
  418.  
  419. static int
  420. GetDeviceCnt(countPtr) 
  421.     int *countPtr;            /* receiving ptr */
  422. {
  423.     int status = -1;
  424.     int mgrSock;
  425.     int retCode = T_SUCCESS;
  426.     int count;
  427.     int i;
  428.     int volId;
  429.     char devName[80];
  430.     char *devPtr = devName;
  431.     char *hostPtr = hostName;
  432.     char *userPtr = userName;
  433.  
  434.     count = -1;
  435.  
  436.     mgrSock = Sock_SetupSocket(parms.mgrPort, parms.mgrServer, 1);
  437.     retCode = Sock_WriteString(mgrSock, userName, 0);
  438.     retCode += Sock_WriteInteger(mgrSock, S_CMDSTAT);
  439.     retCode += Sock_WriteInteger(mgrSock, 0);
  440.     retCode += Sock_ReadInteger(mgrSock, &status);
  441.     if (retCode == T_SUCCESS) {
  442.         Sock_ReadInteger(mgrSock, &count);
  443.     }    
  444.  
  445.     *countPtr = count;
  446.  
  447.     /* throw away device info. We just want the count. */
  448.     for (i=0; i<count; i++) {
  449.     if ((Sock_ReadString(mgrSock, &devPtr, 0) != T_SUCCESS) ||
  450.         (Sock_ReadInteger(mgrSock, &volId) != T_SUCCESS) ||
  451.         (Sock_ReadString(mgrSock, &hostPtr, 0) != T_SUCCESS) ||
  452.         (Sock_ReadString(mgrSock, &userPtr, 0) != T_SUCCESS)) {
  453.         return(T_FAILURE);
  454.     }
  455.     }
  456.  
  457.     if (count > -1) {
  458.     count = -1;
  459.     Sock_ReadInteger(mgrSock, &count);
  460.     }
  461.  
  462.     /* throw away queuedevice info. */
  463.     for (i=0; i<count; i++) {
  464.     if ((Sock_ReadInteger(mgrSock, &volId) != T_SUCCESS) ||
  465.         (Sock_ReadString(mgrSock, &hostPtr, 0) != T_SUCCESS)||
  466.         (Sock_ReadString(mgrSock, &userPtr, 0) != T_SUCCESS)) {
  467.         return T_FAILURE;
  468.     }
  469.     }
  470.  
  471.     close(mgrSock);
  472.  
  473.     return T_SUCCESS;
  474. }
  475.  
  476.  
  477. /*
  478.  *----------------------------------------------------------------------
  479.  *
  480.  * MgrAcquire
  481.  *
  482.  *    Talk to jukebox manager to acquire a volume
  483.  *
  484.  * Results:
  485.  *    return code
  486.  *
  487.  * Side effects:
  488.  *    Acquires or releases volume
  489.  *
  490.  *----------------------------------------------------------------------
  491.  */
  492.  
  493. static int
  494. MgrAcquire(mgrDataPtr)
  495.     MgrData *mgrDataPtr;
  496. {
  497.     int retCode = T_SUCCESS;
  498.     char devName[T_MAXPATHLEN];
  499.     char *devNamePtr = devName;
  500.     int status =T_SUCCESS;
  501.     int sleepSecs = 5;
  502.     int retryCnt = 0;
  503.  
  504.     mgrDataPtr->sock= Sock_SetupSocket(parms.mgrPort, parms.mgrServer, 1);
  505.  
  506.     retCode = Sock_WriteString(mgrDataPtr->sock, userName, 0);
  507.     retCode += Sock_WriteInteger(mgrDataPtr->sock, S_CMDLOCK);
  508.     retCode += Sock_WriteInteger(mgrDataPtr->sock, mgrDataPtr->volId);
  509.     retCode += Sock_ReadInteger(mgrDataPtr->sock, &status);
  510.  
  511.     if ((retCode != T_SUCCESS) || (status != T_SUCCESS)) {
  512.     fprintf(stderr,"Couldn't acquire vol %d. Status %d, errno %d\n",
  513.         mgrDataPtr->volId, status, syserr);
  514.     return T_FAILURE;
  515.     }
  516.  
  517.     Sock_ReadString(mgrDataPtr->sock, &devNamePtr, 0);
  518.  
  519.     /* Must sleep here, waiting for drive to spin up...*/
  520.     while (((mgrDataPtr->stream=open(devName, O_RDWR, 0600)) == -1) &&
  521.        (retryCnt++ < 10)) {
  522.     sleep(sleepSecs);
  523.     }
  524.     if (mgrDataPtr->stream == -1) {
  525.     fprintf(stderr, "Couldn't open device %s. errno %d\n",
  526.         devName, errno);
  527.     return T_IOFAILED;
  528.     }
  529.     return retCode;
  530. }
  531.  
  532.  
  533. /*
  534.  *----------------------------------------------------------------------
  535.  *
  536.  * MgrRelease
  537.  *
  538.  *    Talk to jukebox manager to release a volume
  539.  *
  540.  * Results:
  541.  *    return code
  542.  *
  543.  * Side effects:
  544.  *    Drops connection to jmgr
  545.  *
  546.  *----------------------------------------------------------------------
  547.  */
  548.  
  549. static int
  550. MgrRelease(mgrDataPtr)
  551.     MgrData *mgrDataPtr;
  552. {
  553.     int retCode = T_SUCCESS;
  554.     int status =T_SUCCESS;
  555.  
  556.     if (mgrDataPtr->sock == -1) {
  557.     return T_SUCCESS;
  558.     }
  559.  
  560.     retCode = Sock_WriteString(mgrDataPtr->sock, userName, 0);
  561.     retCode += Sock_WriteInteger(mgrDataPtr->sock, S_CMDFREE);
  562.     retCode += Sock_WriteInteger(mgrDataPtr->sock, mgrDataPtr->volId);
  563.     retCode += Sock_ReadInteger(mgrDataPtr->sock, &status);
  564.     if ((retCode != T_SUCCESS) || (status != T_SUCCESS)) {
  565.     fprintf(stderr,"Couldn't release vol %d. Status %d, errno %d\n",
  566.         mgrDataPtr->volId, status, syserr);
  567.     }
  568.  
  569.     close(mgrDataPtr->sock);
  570.     close(mgrDataPtr->stream);
  571.  
  572.     mgrDataPtr->sock = -1;
  573.     mgrDataPtr->volId = -1;
  574.     mgrDataPtr->stream = -1;
  575.  
  576.     return retCode;
  577. }
  578.  
  579.  
  580.  
  581. /*
  582.  *----------------------------------------------------------------------
  583.  *
  584.  * CheckOptions -- 
  585.  *
  586.  *    Make sure command line options look reasonable
  587.  *
  588.  * Results:
  589.  *    none.
  590.  *
  591.  * Side effects:
  592.  *    none.
  593.  *
  594.  *----------------------------------------------------------------------
  595.  */
  596.  
  597. static void
  598. CheckOptions(parmsPtr)
  599.     Parms *parmsPtr;
  600. {
  601.     if (Utils_CheckName(parmsPtr->mgrServer, 1) == T_FAILURE) {
  602.     sprintf(printBuf, "Bad mgr server name: '%s'.\n",
  603.         parmsPtr->mgrServer);
  604.     Utils_Bailout(printBuf, BAIL_PRINT);
  605.     }
  606.     if ((parmsPtr->mgrPort < 1) || (parmsPtr->mgrPort > SHRT_MAX)) {
  607.     sprintf(printBuf, "Bad mgr port number %d.\n",
  608.         parmsPtr->mgrPort);
  609.     Utils_Bailout(printBuf, BAIL_PRINT);
  610.     }
  611.     if (parmsPtr->srcVol < 0) {
  612.     sprintf(printBuf, "Bad source volume id.\n");
  613.     Utils_Bailout(printBuf, BAIL_PRINT);
  614.     }
  615.     if ((parmsPtr->destVol < 0) && (parmsPtr->destVol != DEF_DESTVOL)) {
  616.     sprintf(printBuf, "Bad destination volume id.\n");
  617.     Utils_Bailout(printBuf, BAIL_PRINT);
  618.     }
  619.     if (parmsPtr->updateFreq <= 0) {
  620.     sprintf(printBuf, "Update frequency must be > 0.\n");
  621.     Utils_Bailout(printBuf, BAIL_PRINT);
  622.     }
  623.     if ((parmsPtr->bufSize <= 0) && (parmsPtr->bufSize % T_TAPEUNIT)){
  624.     sprintf(printBuf, "Need buffer size > 0 and a multiple of %d.\n",
  625.         T_TAPEUNIT);
  626.     Utils_Bailout(printBuf, BAIL_PRINT);
  627.     }
  628. }
  629.  
  630.